package `03-dsl`.`dsl04-html01`

import java.io.File

/**
 * 使用KT高阶函数-自定义语言编写Html
 * 注意：
 * 1.面向对象编程，设计类的关系（依赖倒置原则） 面向抽象不面向细节（抽离出类），面向高层不面向具体（用父类不用子类）
 * 2.面向对象-实现树形结构
 *
 *
 * 需求
 * 1.先存储信息
 * 2.再拼接（递归）
 *
 * 实现：
 * 1.调用 lambda 高阶函数

 * 思路：
 * 把html看成元素，里面包含文本（使用 Kotlin 进行 HTML 编码）、子元素head和子元素body
 * head元素中包含title子元素，title元素中包含文本内容（我是title）
 * body元素中包含h1子元素、a子元素、p元素和ul元素
 * 依次类推 。。。。
 *
 * 抽象出来元素包含 文本内容（+ -）、子元素、元素名称（html、body、head等）、元素属性（a元素）、元素缩进
 * 收集完元素信息，需要根据信息拼接出最终的html
 *
 *
 * 缺点：
 * 1.新增标签 需要新增代码
 * 2.新增属性 需要新增属性代码
 */

/**

 */

//底下是树形结构类的创建
private interface Element{
    /**
     * run 函数拼接html文件
     * @param stringBuild:StringBuilder 存储拼接字符串
     * @param indent:String 缩进
     */
    fun run(stringBuild:StringBuilder,indent:String)
}

//文本元素 特殊元素
private  class TextElement(var text:String):Element{
    /**
     * 拼接 没有子元素
     * 缩进+文本+换行
     */
    override fun run(stringBuild: StringBuilder, indent: String) {
        stringBuild.append("$indent$text\n")
    }
}

//元素的组成，元素名称、子元素、元素属性
private open class Tag(var tagName:String):Element{
    //存储子元素
    val elements = arrayListOf<Element>()

    //元素的属性 key，value
    val attributeList = hashMapOf<String,String>()

    /**
     * 拼接
     * 1.自己
     * 2.子元素
     */
    override fun run(stringBuild: StringBuilder, indent: String) {
        //拼接开始 缩进<html 属性>
        stringBuild.append("$indent<$tagName${handleAtteibure()}>\n")
        //拼接子元素
        for (element in elements) {
            element.run(stringBuild,indent+"   ")
        }
        //拼接闭合 缩进</html>
        stringBuild.append("$indent</$tagName>\n")
    }

    //拼接属性
    fun handleAtteibure():String{
        var attributeStr = StringBuilder()

        for (key in attributeList.keys) {
            attributeStr.append(" $key=\"${attributeList[key]}\"")
        }
        return  attributeStr.toString()
    }
}

//操作符重载，获取文本元素
private open class TagClass(tagName:String):Tag(tagName = tagName){
    /**
     * +"使用 Kotlin 进行 HTML 编码"
     * + 操作符重载，
     * "使用 Kotlin 进行 HTML 编码" 是内容
     * 在operator 操作符重载时this = 内容 = "使用 Kotlin 进行 HTML 编码"
     */
    operator fun String.unaryPlus(){
        elements += TextElement(this)
    }

    operator  fun String.unaryMinus(){
        elements += TextElement(this)
    }
}


//底部是高级函数的实现
private inline  fun  html(charset:String,action:Html.()->Unit):Html{
    val newHtml = Html()
    newHtml.charset = charset
    newHtml.action()
    return newHtml
}

private class Html:TagClass(tagName = "html"){
    //给a元素新增属性href
    var charset:String
        get(){
            //!!  === String? 强转为 String
            return attributeList["charset"]!!
        }
        set(value) {
            attributeList["charset"] = value
        }

    fun  head(action:Head.()->Unit):Unit{
        val newHead = Head()
        //执行head元素
        newHead.action()
        //将head元素添加至html元素的子元素中
        elements += newHead
    }

    fun  body(action:Body.()->Unit):Unit{
        val newBody= Body("body")
        //执行body元素
        newBody.action()
        //将body元素添加至html元素的子元素中
        elements += newBody
    }

    //使用toString开始执行拼接函数 并传入初始值
    override fun toString(): String {
        val builder = StringBuilder()
        run(builder,"")
        return builder.toString()
    }
}

private class Head:TagClass(tagName = "head"){
    fun  title(action:Title.()->Unit):Unit{
        val newTitle = Title()
        //执行title元素
        newTitle.action()
        //将title元素添加至Head元素的子元素中
        elements += newTitle
    }
}

//title元素中只会有文本元素，已在TagClass类中实现
private class Title:TagClass(tagName = "title")

/**
 * body中可以包含的子元素，h1、a、p、ul、b元素中都也可以互相保安，所以都可以继承子Body类，避免重复写类似内容
 */
private open class Body(tagName: String):TagClass(tagName = tagName){
    fun  h1(action:H1.()->Unit):Unit{
        val newH1 = H1()
        //执行h1元素
        newH1.action()
        //将h1元素添加至当前元素的子元素中
        elements += newH1
    }

    fun  a(href:String,action:A.()->Unit):Unit{
        val newA = A()
        //保存a元素的href属性值
        newA.href = href
        //执行a元素
        newA.action()
        //将A元素添加至当前元素的子元素中
        elements += newA
    }

    fun  p(action:P.()->Unit):Unit{
        val newP = P()
        //执行p元素
        newP.action()
        //将p元素添加至当前元素的子元素中
        elements += newP
    }

    fun  ul(action:UL.()->Unit):Unit{
        val newUL = UL()
        //执行ul元素
        newUL.action()
        //将ul元素添加至当前元素的子元素中
        elements += newUL
    }

    fun  b(action:B.()->Unit):Unit{
        val newB = B()
        //执行b元素
        newB.action()
        //将b元素添加至当前元素的子元素中
        elements += newB
    }
}

private class H1:Body("h1")

private class A:Body( "a"){
    //给a元素新增属性href
    var href:String
        get(){
            //!!  === String? 强转为 String
            return attributeList["href"]!!
        }
        set(value) {
            attributeList["href"] = value
        }
}

private class P:Body( "p")

private class UL:Body("ul"){
    fun  li(action:LI.()->Unit):Unit{
        val newLI = LI()
        //执行li元素
        newLI.action()
        //将li元素添加至当前元素的子元素中
        elements += newLI
    }
}

private class LI:TagClass("li")

private class B:Body("b")

fun main() {
    val names = listOf("Derry1", "Derry2", "Derry3") // 集合

    var result =
        //元素html
        html(charset="utf-8"){
            //文本元素 操作符重载 +  特殊元素，不用拼接元素名称，标签，只需要文本内容
            +"使用 Kotlin 进行 HTML 编码"
            head{
                title {
                    +"我是title"
                }
            }
            body{
                h1{
                    + "我是标题"
                }
                //a元素，特殊元素，有属性
                a(href = "http://bbs.xiangxueketang.cn/pins/recommended"){
                    -"享学论坛"
                }

                p{
                    //文本元素 操作符重载 -
                    - "Derry老师来了"
                    b { -"Derry是谁" }
                    //文本元素 操作符重载 -
                    - "文本。有关更多信息，请参阅"
                    a(href = "http://www.xiangxueketang.cn/") { -"湖南享学" }
                    - "Derry的项目"
                }

                ul{
                    -"我是列表："
                    for (name in names)
                        li { - name }
                }
            }

        }

    println(result)

    val file = File("testhtml.html")
    file.writeText(result.toString())
}